Processing math: 94%
Programmation Python <canvas class="pong" width="1000px" height="500px"></canvas>
Ce document est un tutoriel permettant de découvrir le langage de programmation Python au travers d'un objectif : programmer le jeu « Pong » avec la librairie Pygame sous l'environnement de développement EduPython. Ce document a été utilisé par plusieurs enseignants pour des élèves de seconde, 1ère et terminale, ainsi que des étudiants en DSAA n’ayant aucune formation poussée en science. Ce document est donc destiné aux débutants en programmation. Suivre la construction pas à pas d'un jeu comme Pong devrait permettre de comprendre et manipuler les notions de base de la programmation (boucles, tests conditionnels, etc.) ainsi que des notions plus avancées (animations, évènements clavier-souris, notions de programmation objet). Le tutoriel vous accompagnera pour écrire le programme du jeu Pong. Il est découpé en étapes clés (créer une fenêtre de jeu, tracer une balle, animer un objet, contrôles au clavier, etc.) et il sera possible d'avancer à votre rythme sans jamais être bloqué. En effet, chaque partie sera organisée de la manière suivante :
  • Objectif : par exemple, afficher une balle
  • Explication théoriques : par exemple, comment positionner et donner une couleur à une figure géométrique
  • Exercices : par exemple, tracer des cercles de couleurs différentes à différentes positions de la fenêtre
  • Correction ou indices

Première prise en main

Edupython est une distribution clé en main développée par le groupe AmiensPython. Il comprend des librairies utiles pour la programmation, les sciences et les mathématiques au lycée, ainsi que la librairie Pygame permettant de développer des jeux en 2D. Il est à la fois léger, complet et propose une version portable. C'est donc un excellent choix pour pratiquer la programmation au lycée et au delà. Si vous suivez ce tutoriel de chez vous, commencez par installer Edupython sur votre machine

L'interface de développement ou IDE

L’interface de développement est assez intuitive (voir figure ci-dessous). Les programmes seront rédigés dans la zone d'édition. Parfois des petits tests pourront être réalisés dans la console. Il est possible de gérer plusieurs fichiers à l'aide de la barre d'onglets. Un gestionnaire de variable permet également de mieux déboguer ses programmes.
Le rendu du programme s’obtient simplement en cliquant sur le bouton « run » ou avec le raccourci clavier Ctrl + F9 Ouvrez Edupython. Dans la zone d'édition, entrez la commande suivante :
print("Bonjour, je vais bientôt programmer le jeu Pong !!!")
Enregistrez ce fichier dans un nouveau dossier nommé pong, et l'appeler premier_programme.py Exécutez le programme. A quoi sert la commande
print()
?
Nous proposons de créer une fenêtre d'affichage de 200 pixels par 200 pixels, un fond blanc et une balle rouge de rayon 20 pixels dont le centre à pour coordonnées (100,100). Le contenu graphique dans la fenêtre d'affichage nécessite d'introduire la manière de représenter numériquement les couleurs. La partie suivante y est consacrée. Afin de réalisé l'objectif précédent, il est important de bien comprendre comment sont positionnés les objets dans le plan, et la manière de représenter les couleurs :

Couleurs

Comment sont représentées les couleurs numériquement ?

Fenêtre d'affichage

Positionnement dans une fenêtre d'affichage.

Tout jeu utilisant la librairie Pygame commencera de la même manière : un programme minimal qui consiste à ouvrir une fenêtre d'affichage avec un fond blanc.
  • Ouvrir l'IDE
  • Dans la zone d'édition, taper le programme ci-dessous
  • L'enregistrer dans un dossier nommé pong, et l'appeler pong0.py
  • Exécuter le programme en appuyant sur run
  • Admirer :)
#importation de la librairie pygame
import pygame
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = pygame.display.set_mode([500,500])
#remplissage du fond par la couleur définie
screen.fill([255,255,255])
#activer le rendu graphique (sans lequel rien n'est affiché)
pygame.display.flip()
Changer la couleur du fond. Changer la taille de la fenêtre du jeu. Pour l'instant, fermer la fenêtre du jeu génère un bug. Nous le règlerons à la partie suivante.
L'objectif de tracer une balle rouge dans la fenêtre n'est pas encore rempli. Quand on découvre un langage de programmation, on sait ce qu'on peut faire (ici tracer un disque rouge), mais rarement comment. C'est pourquoi il est important de savoir utiliser la documentation pour trouver ces réponses :

Documentation Pygame

Découvrir la documentation de Pygame...

Après avoir découvert commenter tracer un cercle dans la documentation, ajouter la ligne de code encadrée au fichier précédent pong0.py, comme indiqué ci-dessous :
#importation de la librairie pygame
import pygame
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = pygame.display.set_mode([500,500])
#remplissage du fond par la couleur définie
screen.fill([255,255,255])

# dessin d'un cercle avec les arguments suivant :
# screen : nom de la fenêtre
# [255,0,0] : couleur rouge
# [50,60] : position du centre x=50 et y=60
# 20 : rayon du cercle
# 0 : épaisseur du contour (0 pour aucun contour)
pygame.draw.circle(screen, [255,0,0] , [50,60], 20, 0)


#activer le rendu graphique (sans lequel rien n'est affiché)
pygame.display.flip()
code source : pong0.py Modifier la couleur de la balle. Modifier le rayon de la balle. Réaliser la figure suivante dans un nouveau fichier nommé
cible.py
:
code source : cible.py
La librairie Pygame commence à rendre le code compliqué avec ses longues expressions comme
pygame.draw.circle()
pour tracer un cercle, ou encore
pygame.display.flip()
. Il est possible de raccourcir ces écritures :
On souhaite simplifier le code en raccourcissant les longues instructions Pygame. Pour remplir cet objectif, il faut comprendre comment sont structurées les librairies Python comme Pygame :

Modules et librairies

L'utilisation de librairies en Python...

En appelant différemment nos librairies, simplifier le programme pong précédent comme indiqué ci-dessous :
#importation de la librairie pygame
import pygame
from pygame.draw import *
from pygame.display import *
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode([500,500])
#remplissage du fond par la couleur définie
screen.fill([255,255,255])

# dessin d'un cercle avec les arguments suivant :
# screen : nom de la fenêtre
# [255,0,0] : couleur rouge
# [50,60] : position du centre x=50 et y=60
# 20 : rayon du cercle
# 0 : épaisseur du contour (0 pour aucun contour)
circle(screen, [255,0,0] , [50,60], 20, 0)


#activer le rendu graphique (sans lequel rien n'est affiché)
flip()
code source : pong0.1.py
La fenêtre de dessin semble toujours avoir du mal à se fermer. Dans la partir suivante, nous réglons ce problème. Nous souhaitons quitter le jeu en cliquant sur la croix de la fenêtre générer d'erreur. Pour quitter le programme sans bug, il faut :
  • Fermer en premier la fenêtre d'affichage avec
    pygame.display.quit()
  • Quitter le programme avec
    sys.exit()
    provenant du module
    sys
Attention ! Si on ajoute ces deux lignes de code à notre programme, la fenêtre s'ouvre et se ferme aussitôt. Il faut donc ajouter une temporisation de quelques secondes. On utilisera pour ça la fonction
delay()
de la librairie
time
.
Modifiez le programme comme indiqué ci-dessous afin que la fenêtre se ferme au bout de deux secondes d'affichage :
#importation des librairies pygame et sys
import pygame,sys
from pygame.draw import *
from pygame.display import *

#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode([500,500])
#remplissage du fond par la couleur définie
screen.fill([255,255,255])

# dessin d'un cercle avec les arguments suivant :
# screen : nom de la fenêtre
# [255,0,0] : couleur rouge
# [50,60] : position du centre x=50 et y=60
# 20 : rayon du cercle
# 0 : épaisseur du contour (0 pour aucun contour)
circle(screen, [255,0,0] , [50,60], 20, 0)


#activer le rendu graphique (sans lequel rien n'est affiché)
flip()
pygame.time.delay(2000)#attendre deux secondes
pygame.display.quit()#fermer la fenêtre
sys.exit()#arrête le programme
code source : pong0.2.py
L'objectif ici est de mettre en place le mouvement en pouvant faire varier les attributs de la balle (position, vitesse, couleur, etc.). Commençons par détailler les attributs naturels de la balle : on considère qu'à l'instant t, la balle est à la position (x,y) dans un repère orthogonal (O;I;J). A la frame suivante, à l'instant t+dt, sa nouvelle position est (x+dx;y+dy). La balle est caractérisée par :
  • Sa position (x,y)
  • Son déplacement (dx,dy)
  • Sa couleur (r,g,b)
  • Sa forme, qu'on considèrera ici comme circulaire.
La balle est également caractérisée par des actions : La balle réalisera les actions suivantes :
  • Afficher : Cela paraît trivial, mais il est tout d’abord nécessaire de dessiner la balle pour qu’elle apparaisse sur la fenêtre d’affichage. Il faut alors un cercle de rayon r, à la position (x,y), rempli avec la couleur c.
  • Avancer ou bouger : Faire avancer la balle de dx suivant l’axe x et de dy suivant l’axe y, entre deux frames consécutives.
  • Rebondir ou Test de collision : Faire rebondir la balle sur les bords de la fenêtre d’affichage. Par la suite, nous ferons évoluer cette action en permettant la collision avec une raquette. Nous pourrons aussi rajouter du son à chaque collision.
On peut aussi définir un vecteur position \vec{p} = \left(\begin{array}{c} x \\y \end{array}\right) ainsi qu’un vecteur déplacement \vec{d} = \left(\begin{array}{c} dx \\dy \end{array}\right). On considère dans un premier temps deux paramètres au lieu d’un vecteur. Attention, dans les représentations d'images numériques et les logiciels de traitement d'image, l'axe des ordonnées est orienté vers le bas.
Pour représenter informatiquement les attributs naturels de la balle, nous avons besoin pour les représenter de manière informatique d'introduire les notions de variables et de types :

Variables Python

Les différents types de variables que l'on peut rencontrer en programmation.

Les variables caractérisant notre balles seront celles-ci :
Attributs de la balle Nom de la variable Type de la variable
Attributs de la balle
Couleur
Nom de la variable
c
Type de la variable
Liste de 3 entiers
int
Attributs de la balle
Rayon
Nom de la variable
r
Type de la variable
int
Attributs de la balle
Position (abscisse)
Nom de la variable
x
Type de la variable
int
Attributs de la balle
Position (ordonnée)
Nom de la variable
y
Type de la variable
int
Attributs de la balle
Déplacement (abscisse)
Nom de la variable
dx
Type de la variable
int
Attributs de la balle
Déplacement (ordonnée)
Nom de la variable
dy
Type de la variable
int
Attributs de la balle
Forme
Nom de la variable
cercle (pour l'instant cette variable ne changera pas)
Type de la variable
---
Ces variables sont déclarées en début de programme, et utilisées par la suite dans toute le programme. C'est ce qu'on qualifie de variable globale. Modifier le code comme indiqué ci-dessous afin d'utiliser des variables pour définir les attributs de la balle : Le code est le suivant :
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *
# variables globales #############################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
#attributs de la balle
[dx,dy] = [2, 1]#vecteur de déplacement entre deux frames consécutives
couleurFond = [255, 0, 0]#rouge
couleurBalle= [0,0,255]#bleu
[x,y]=[100,100] # position initiale de la balle
r=20 #rayon de la balle

#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
#remplissage du fond par la couleur définie
screen.fill(couleurFond)
# dessin d'un cercle :
# 0 : dernier paramètre - épaisseur du contour (0 pour aucun contour)
circle(screen, couleurBalle , [x,y], r, 0) # voir documentation : https://www.pygame.org/docs/ref/draw.html
#activer le rendu graphique (sans lequel rien n'est affiché)
flip()
pygame.time.delay(2000)#attend deux secondes
pygame.display.quit()#ferme la fenêtre
sys.exit()#arrête le programme
code source : pong0.3.py
L'objectif est maintenant de faire avancer la balle. Il va donc falloir simuler le mouvement par la sucession rapide de plusieurs images à la manière d'un film. Une fréquence de rafraîchissement de 30 images par seconde donne un rendu suffisamment fluide.

Actuellement, l'image est tracée une seule fois. Il va falloir utiliser une boucle infinie modifiant le contenu de la fenêtre d'affichage 30 fois par seconde. Celà signifie que chaque itération de la boucle doit durer \frac{1}{30}-ème de seconde.

Cette boucle de jeu sera créee à l'aide d'une boucle "While" qui vous est présentée ici :

Boucle While

Présentation de la boucle while permettant de répéter des instructions de manière répétée...

Pour commencer, nous souhaitons mettre la balle en mouvement comme ceci : Entre chaque frame la balle doit se déplacer de
dx
suivant l’axe x et
dy
suivant l’axe y. Il faut donc rajouter dans la boucle infinie les instructions suivantes :
x = x+dx
y = y+dy
Pour donner l'illusion du mouvement, modifiez le programme comme indiqué ci-dessous afin de :
  1. Mettre en place la boucle infinie
  2. Afficher la balle à chaque itération
  3. Déplacer la balle
  4. Attendre 15 ms (\simeq 60 fps)
  5. Rafraîchir le contenu de la fenêtre
  6. Test d'arrêt pour sortir de la boucle et fermer Pygame
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *
# variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
#attributs de la balle
[dx,dy] = [2, 1]#vecteur de déplacement entre deux frames consécutives
couleurFond = [255, 0, 0]#rouge
couleurBalle= [0,0,255]#bleu
[x,y]=[100,100] # position initiale de la balle
r=20 #rayon de la balle
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
#remplissage du fond par la couleur blanche
screen.fill(couleurFond)
while True:
	#afficher la balle :
	circle(screen, couleurBalle , [x,y], r, 0)# dessin d'un cercle :
	#déplacement de la balle :
	x=x+dx
	y=y+dy
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test d'arrêt du jeu
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
code source : pong1.0.py

Normalement, le résultat est le suivant :

Nous remarquons que la balle laisse la trace de toutes les positions successive au cours du mouvement. Pour effacer les traces, il faut redessiner la couleur du fond à chaque début de boucle avant d'afficher la balle. Ajouter une instruction permettant d'"effacer" la trace laissée derrière la balle.
Ce qui donne le code suivant :
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *
# variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
#attributs de la balle
[dx,dy] = [2, 1]#vecteur de déplacement entre deux frames consécutives
couleurFond = [255, 0, 0]#rouge
couleurBalle= [0,0,255]#bleu
[x,y]=[100,100] # position initiale de la balle
r=20 #rayon de la balle
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
while True:
	#remplissage du fond par la couleur blanche
screen.fill(couleurFond)
	#afficher la balle :
	circle(screen, couleurBalle , [x,y], r, 0)# dessin d'un cercle :
	#déplacement de la balle :
	x=x+dx
	y=y+dy
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test d'arrêt du jeu
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
code source : pong1.1.py
Il existe des opérateurs d’assignation qui permettent de simplifier l’écriture
x = x+dx
et
y=y+dy
. Ils se résument dans le tableau suivant :
Attribution à x d'une valeur a Ajouter a à une variable x Retirer a à une variable x Multiplier une variable x par a Diviser une variable x par a
Attribution à x d'une valeur a
x=a
Ajouter a à une variable x
x+=a
Retirer a à une variable x
x-=a
Multiplier une variable x par a
x*=a
Diviser une variable x par a
x/=a
La partie « faire avancer la balle » du code précédent peut se remplacer par :
#déplacement de la balle :
x+=dx
y+=dy
La balle ne rebondit pas encore sur les bords. C’est l’objet de cette partie. On souhaite simuler le rebond sur bord droit comme ceci : L'objectif est de trouver une manière de détecter lorsque la balle heurte le bord de l'écran afin de lui faire changer de sens horizontalement.

Convention de déplacement suivant x

D’après l’orientation des axes (voir partie III-1) :
  • si la balle se déplace de gauche à droite, alors
    dx
    est positif
  • si la balle se déplace de droite à gauche, alors
    dx
    est négatif

Condition de rebond

L'algorithme est le suivant :
si (la balle touche le bord)
alors
	le sens de déplacement change de signe (sens opposé)
fin si
Mathématiquement, le changement de signe de dx revient à multiplier cette valeur par -1. On écrit :
dx ← dx * (-1)
En python, on peut l'écrire de différentes manières :
dx = dx * (-1)
dx *= (-1)
dx = -dx
Il reste à trouver à quel moment la balle touche le bord, ou quand elle est sur le point de le dépasser :
  • à la frame d’avant la collision (instant t_{n-1}), le bord droit de la balle (point M_{n-1}) se situe à une distance a du bord de la fenêtre d’affichage, telle que 0 \leq a \lt dx
  • et à l’instant d’après la collision(instant t_n), le bord droit de la balle (point M_n) se situe à une distance b du bord de la fenêtre d’affichage, telle que 0 \leq b \lt dx.
Ce qui est résumé par l'image suivante : Dans la figure précédente, on remarque qu’à l’instant t_n, lors de la collision, le point M_n d’abscisses x+r dépasse le bord droit de la fenêtre d’affichage d'abscisse x=width.

On considère que la balle entre en collision avec le bord lorsque la condition suivante est vérifiée : x+r >width

Algorithme de la collision sur le bord vertical droit de la fenêtre d’affichage

L’algorithme de collision sur le bord droit est donc le suivant :
si la condition (x+r >width) est vraie alors
	dx  ← -dx
fin si
On modifie la boucle infinie pour introduire la condition de rebond à droite : Modifier le programme précédent afin d'ajouter le rebond à droite comme indiqué ci-dessous :
while True:
#remplissage du fond par la couleur blanche
screen.fill(couleurFond)
#afficher la balle :
circle(screen, couleurBalle , [x,y], r, 0)# dessin d'un cercle :
#déplacement de la balle :
x=x+dx
y=y+dy
#test de collision bords droit
if x+r > width:
   dx=-dx
flip()#activer le rendu graphique
pygame.time.delay(15)#une image toute les 15 ms
#test d'arrêt du jeu
for event in pygame.event.get():
	if event.type == pygame.QUIT:#si clique sur la croix quitter
		pygame.display.quit()#ferme la fenêtre
		sys.exit()#arrête le programme
code source : pong1.2.py
On souhaite ajouter le rebond sur le bord gauche : En s'inspirant de la partie précédente, détecter et réaliser le rebond sur le bord vertical gauche.

Condition de rebond

Comme pour la collision sur le bord droit :
  • à la frame d’avant la collision (instant t_{n-1}), le bord gauche de la balle (point N_{n-1}) se situe à une distance a du bord de la fenêtre d’affichage, telle que 0 \leq a \lt dx,
  • et à l’instant d’après la collision (instant t_n), le bord gauche de la balle (point N_n) se situe à une distance b du bord de la fenêtre d’affichage, telle que 0 \leq b \lt dx.
Dans la figure suivante, on remarque qu’à l’instant t_n, lors de la collision, le point N_n d’abscisses x-r dépasse le bord droit de la fenêtre d’affichage représenté par la droite d’équation x=width.

On considère que la balle entre en collision avec le bord lorsque la condition suivante est vérifiée : (x-r \lt 0)

Algorithme de la collision sur le bord vertical droit de la fenêtre d’affichage

L’algorithme de collision sur le bord droit est donc le suivant :
si la condition (x-r < 0) est vraie alors
	dx  ← -dx
fin si
On modifie la boucle infinie pour introduire la condition de rebond à gauche :
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	#afficher la balle :
	circle(screen, couleurBalle , [x,y], r, 0)# dessin d'un cercle :
	#déplacement de la balle :
	x=x+dx
	y=y+dy
	#test de collision bord droit
	if x+r > width:
	   dx=-d
	
	#test de collision bord gauche
if x-r < 0:
   dx=-dx

	
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test d'arrêt du jeu
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme

code source : pong1.3.py

Si l'une des condition
x+r > width
OU
x-r < 0
est remplie, elle réalisera la même instruction
dx = -dx
. On peut regrouper ces deux conditions en un seul test :
si (x+r > width) OU (x-r < 0) alors
	dx  ← -dx
fin si
Ce qui peut être traduit de la manière suivante en python :
while True:
#remplissage du fond par la couleur blanche
screen.fill(couleurFond)

#afficher la balle :
circle(screen, couleurBalle , [x,y], r, 0)# dessin d'un cercle :

#déplacement de la balle :
x=x+dx
y=y+dy

#test de collision bords droite et gauche
if (x+r > width) or (x-r < 0):
   dx=-dx

flip()#activer le rendu graphique

pygame.time.delay(15)#une image toute les 15 ms

#test d'arrêt du jeu
for event in pygame.event.get():
	if event.type == pygame.QUIT:#si clique sur la croix quitter
		pygame.display.quit()#ferme la fenêtre
		sys.exit()#arrête le programme
code source : pong1.4.py
On souhaite ajouter les rebonds sur les bords du haut et du bas : En s'inspirant des deux parties précédentes, proposer une modification du code afin de réaliser les rebonds sur les bords horizontaux.
Tout le raisonnement effectué pour la collision sur les bords verticaux se transpose intégralement par analogie sur les bords horizontaux. Dans les deux lignes de code correspondant à l’action « Test de collision suivant x », il suffit d’effectuer les changements de variable suivants : \begin{array}{ccc} \text{Axe } x & & \text{Axe }y\\ \hline x & \leftrightarrow & y \\ dx & \leftrightarrow & dy \\ width & \leftrightarrow & height \end{array} Le code Python est le suivant :
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	#afficher la balle :
	circle(screen, couleurBalle , [x,y], r, 0)# dessin d'un cercle :
	#déplacement de la balle :
	x=x+dx
	y=y+dy
	
	#test de collision bords droite et gauche
	if (x+r > width) or (x-r < 0):
	   dx=-dx
	   
	#test de collision bords haut et bas
if (y+r > height) or (y-r < 0):
   dy=-dy
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test d'arrêt du jeu
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
code source : pong1.5.py
Lors des différents tests conditionnels, nous avons introduits un certain nombre d’opérateurs de comparaison (relationnels) qui peuvent se résumer dans le tableau suivant :
supérieur inférieur supérieur ou égal inférieur ou égal égalité
supérieur
>
inférieur
<
supérieur ou égal
>=
inférieur ou égal
<=
égalité
==
Nous avons introduit aussi un certain nombre d’opérateurs arithmétique :
addition soustraction multiplication division modulo (reste de la division euclidienne)
addition
+
soustraction
-
multiplication
*
division
/
modulo (reste de la division euclidienne)
%
Enfin nous avons introduits des opérateurs d’assignation :
Attribution à x d'une valeur a Ajouter a à une variable x Retirer a à une variable x Multiplier une variable x par a Diviser une variable x par a
Attribution à x d'une valeur a
x=a
Ajouter a à une variable x
x+=a
Retirer a à une variable x
x-=a
Multiplier une variable x par a
x*=a
Diviser une variable x par a
x/=a
Proposer une modification du code précédent pour que :
  • la balle ne rebondisse plus
  • mais qu'elle soit "téléportée" sur le bord opposé (comme pour pacman)
L'animation voulue est celle-ci : Le code Python est le suivant :
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	#afficher la balle :
	circle(screen, couleurBalle , [x,y], r, 0)# dessin d'un cercle :
	#déplacement de la balle :
	x=(x+dx)%width
y=(y+dy)%height
	
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test d'arrêt du jeu
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
code source : pong-teleportation.py

On souhaite maintenant ajouter des balles au jeu. Chaque balle doit avoir 5 attributs (positions [x,y], vitesses [dx, dy], rayon r). Actuellement, Si on veut afficher 10 balles, cela revient à manipuler 5*10 = 50 variables différentes. De même, chaque balle doit avoir des actions propres (afficher, avancer, test de collision) qu'il faudrait dupliquer.

Une méthode grossière consiste à copier chaque partie du code réalisé pour la première balle et à l’adapter pour chaque balle. Cela conduirait à un code très long, répétitif et fastidieux à lire.

Une des solutions pour remédier à ce problème est :
  • l’utilisation de « fonctions d’action » génériques et réutilisables pour chaque balle
  • L'utilisation de listes de variables qui regroupent les attributs de toutes les balles en un seul objet.
  • l'utilisation d'une boucle « for » qui permet d’effectuer de manière itérative les mêmes tâches répétitives pour chaque balle.
Nous introduisons dans un premier temps la notion de fonctionpour la balle unique créée dans la partie précédente. Nous introduisons ensuite la notion de liste et de boucle for pour gérer les balles multiples. Lorsque la balle évolue dans la fenêtre d’affichage, pour chaque itération de la boucle while, on peut résumer notre programme par les actions élémentaires suivantes :
Pour chaque itération de la boucle :
	Afficher la balle
	Avancer
	Tester la collision
Fin pour

Il suffit pour cela d’incorporer les différentes lignes de codes écrites pour chaque actions dans la boucle while dans des fonctions indépendantes qui peuvent être appelées à n’importe quel instant.

De manière générale, une fonction peut avoir des paramètres en entrée et renvoyer des paramètres en sortie. Dans le cas présent, dans le cas d’une balle unique, on pourra définir les fonctions suivantes :
  • afficher()
    : permet de dessiner la balle
  • avancer()
    : permet de faire avancer la balle
  • testCollision()
    : teste la collision sur les bords
A priori, aucune de ces méthodes ne prend de paramètre en entrée et ne renvoie de paramètre en sortie. Nous avons déjà utilisé quelques fonctions prédéfinies, issues de la libraire Pygame comme :
  • fill(color)
    : donne une couleur color au fond
  • circle(screen, couleurBalle , [x,y], r, 0)
    : dessine un cercle sur l'écran à une position donnée, une couleur et un rayon.
  • etc.
Nous allons maintenant créer nos propres fonctions.
Les trois fonctions doivent être déclarées avant leur exécution (donc au dessus de la boucle while). Par exemple, pour la fonction
afficher()
, nous procédons de la manière suivante :
  • On définit la méthode
    afficher()
    avec le mot clé
    def
  • On déplace les lignes de codes correspondant à la partie « affichage de la balle », à l’intérieur des accolades d’encapsulation de la fonction afficher().
On procède de la même manière pour les deux autres comme ceci : Le code est le suivant :
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *

# variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage

#attributs de la balle
[dx,dy] = [3,1]#vecteur de déplacement entre deux frames consécutives
couleurFond = [255, 0, 0,10]#rouge
couleurBalle= [0,0,255]#bleu
[x,y]=[100,100] # position initiale de la balle
r=20 #rayon de la balle

# Nos fonctions d'actions
def afficher():
	circle(screen, couleurBalle , [x,y], r, 0)
	
def avancer():
	x=x+dx
	y=y+dy
	
def testCollision():
	#bords verticaux
	if x+r > width or x-r < 0:
	   dx=-dx
	#bords horizontaux
	if y+r > height or y-r < 0:
	   dy=-dy
	   
#lancement des fonctionnalités de la librairie pygame
pygame.init()

#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)

while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	
	#afficher la balle :
afficher()
#déplacement de la balle :
avancer()
#test de collision
testCollision()

	
	flip()#activer le rendu graphique
	
	pygame.time.delay(15)#une image toute les 15 ms
	
	#test d'arrêt du jeu
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
code source : pong0=1.6.1.py Si vous avez exécuté le programme précédent avec les trois nouvelles fonctions, vous avez rencontré l'erreur suivante :
UnboundLocalError: local variable 'x' referenced before assignment

C'est normal ! Celà signifie que la variable

x
est inconnue dans la fonction
afficher()
.

En effet, les variables utilisées dans chaque fonction ont par défaut une portée locale. Celà signifie qu'elle doivent être déclarée et utilisée à l'intérieur de la fonction. Elle n'existent pas en dehors de cette fonction.

Si une variable, déclarée a l'extérieur, doit être utilisée et modifiée à l'intérieur de la fonction il est nécessaire de la déclarée comme variable globale en début de fonction comme ceci :

global x,y,dx,dy

Il suffit d'ajouter une ligne au début de chaque fonction pour que le programme fonctionne :
# Nos fonctions d'actions
def afficher():
	global x,y,couleurBalle,r
	circle(screen, couleurBalle , [x,y], r, 0)
def avancer():
	 global x,y,dx,dy
	x=x+dx
	y=y+dy
def testCollision():
	global x,y,width,height,r,dx,dy
	#bords verticaux
	if x+r > width or x-r < 0:
	   dx=-dx
	#bords horizontaux
	if y+r > height or y-r < 0:
	   dy=-dy
code source : pong0=1.6.2.py
Créer une fonction
testEvenement()
qui s'occupe de la détection des évènements clavier/souris et qui gère l'arrêt du jeu et la fermeture de la fenêtre.
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *
# variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
#attributs de la balle
[dx,dy] = [3,1]#vecteur de déplacement entre deux frames consécutives
couleurFond = [255, 0, 0,10]#rouge
couleurBalle= [0,0,255]#bleu
[x,y]=[100,100] # position initiale de la balle
r=20 #rayon de la balle
# Nos fonctions d'actions
def afficher():
	circle(screen, couleurBalle , [x,y], r, 0)
def avancer():
	x=x+dx
	y=y+dy
def testCollision():
	#bords verticaux
	if x+r > width or x-r < 0:
	   dx=-dx
	#bords horizontaux
	if y+r > height or y-r < 0:
	   dy=-dy

def testEvenement():
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme

   
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
		
	#afficher la balle :
	afficher()
	#déplacement de la balle :
	avancer()
	#test de collision
	testCollision()
	
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test d'arrêt du jeu
	testEvenement()
code source : pong0=1.6.3.py
Nous allons maintenant faire évoluer le programme de sorte qu’il puisse gérer un nombre N de balles. Pour cela, il est nécessaire d’introduite la notion de listes. Nous allons tout d’abord créer, puis manipuler 3 balles dont la gestion sera effectuée par l’utilisation de listes. Nous créerons ensuite un grand nombre de balles et introduiront la boucle itérative « for » pour pouvoir les manipuler. Comme nous l’avons vu précédemment, chaque balle dispose de 6 attributs qui la caractérisent de manière unique. Si on décide de faire évoluer N=10 balles, cela revient à manipuler 6*10 = 60 variables différentes, ce qui est très lourd.

La solution est de définir une liste de valeurs pour chaque attribut de la balle. Si nous manipulons N balles, chaque liste comportera N valeurs de même type. Chaque liste est alors considéré comme une variable unique qui permet de manipuler N valeurs.

Quel que soit le nombre N de balles, l’utilisation des listes permet alors de ne manipuler que 6 variables correspondant à chaque attribut d’une balle.

Pour découvrir les listes, nous allons utiliser la console de l'IDE :

Dans la console, créer une liste
L
de 3 valeurs (qui pourrait représenter une couleur RVB)
>>> L = [20,10,15]
Reprenons l'exemple précédent (
L
étant toujours en mémoire dans la console) : Il est possible d'accéder à un élément d'une liste en précisant son indice entre crochets
[]
. Taper la commande suivante :
>>> L[0]
Elle doit renvoyer
20
car le premier élément de la liste est repéré par l'indice
0
Il est possible de modifier un élément d'une liste avec une simple affectation. Taper la commande suivante :
>>> L[0] = "pong is the best game !!"
Afficher la liste
L
Elle vaut maintenant
['pong is the best game!!', 10, 15]
.
Les listes en Python peuvent être composées d'objets de types différents. Ici
L
contient une chaîne de caractère et deux entiers.
Commande Commentaire
Commande
len(L)
Commentaire
La fonction
len
renvoie la longueur de la liste
L
Commande
L.append(val)
Commentaire
ajouter une valeur
val
après le dernier élément de la liste
Commande
L.insert(i,val)
Commentaire
ajouter une valeur
val
après le i-ème élément de la liste
Commande
L+=L0
Commentaire
concaténer la liste
L0
à la liste
L
Commande
del(L[i])
Commentaire
retirer le i-ème élément de la liste
L
Commande
a=L.pop()
Commentaire
retirer le dernier élément de la liste
L
et le stocke dans
a
.
Commande
L.remove(val)
Commentaire
retirer la première occurence d'un élément ayant la même valeur que
val
.
Commande
L.reverse()
Commentaire
renverse l'ordre
Commande
L.sort()
Commentaire
réordonne les éléments de la liste
Commande
val in L 
Commentaire
renvoie
True
si un élément de la liste vaut
val
,
False
sinon.
Commande
val not in L
Commentaire
renvoie
False
si un élément de la liste vaut
val
,
True
sinon.
Pour être sûr d'avoir bien compris, on peut s'entraîner : Dans la console, utiliser les commandes précédentes pour : Afficher la longueur de
L
Ajouter la valeur
42
à la fin de la liste
Ajouter la valeur
3.141592
en 2ème position de la liste (attention le "premier" est
L[0]
)
Retirer le 3-ème élément de la liste
>>> len(L)
>>> L.append(42)
>>> L.insert(1,3.141592)
>>> del(L[2])
ou
L.remove(3.141592)
On va initialiser (avec des valeurs quelconques) des listes représentant les différents attributs de la balle :
  • dx
    : listes des déplacements horizontaux entre deux frames
  • dy
    : liste des déplacement verticaux entre deux frames
  • x
    : liste des abscisses des positions
  • y
    : liste des ordonnées des positions
  • r
    : liste des rayons des balles
Pour commencer, on donne la même couleur à chaque balle

Initialisation des listes

On donne 3 valeurs quelconques pour chaque attribut :
dx = [3,2,5] #vecteur de déplacement entre deux frames consécutives
dy = [-1, 1,3]
x = [100, 250, 50]
y = [30, 400, 100]
r = [20, 25, 15]

Modification des fonctions d'action

Il faut préciser à chaque fonction l'indice
i
de la balle sur laquelle elle s'applique. On ajoute un argument
i
en entrée de la fonction :
def afficher(i):
	global x,y,couleurBalle,r
	circle(screen, couleurBalle , [x[i],y[i]], r[i], 0)
	
def avancer(i):
	global x,y,dx,dy
	x[i]=x[i]+dx[i]
y[i]=y[i]+dy[i]
	
def testCollision(i):
	global x,y,width,height,r,dx,dy
	#bords verticaux
	if x[i]+r[i] > width or x[i]-r[i] < 0:
   dx[i]=-dx[i]
#bords horizontaux
if y[i]+r[i] > height or y[i]-r[i] < 0:
   dy[i]=-dy[i]

Appels des fonctions d'action dans la boucle infinie

while True:
		#remplissage du fond par la couleur blanche
		screen.fill(couleurFond)
		
		#afficher la balle :
afficher(0)
afficher(1)
afficher(2)

#déplacement de la balle :
avancer(0)
avancer(1)
avancer(2)

#test de collision
testCollision(0)
testCollision(1)
testCollision(2)
		
		flip()#activer le rendu graphique
		
		pygame.time.delay(15)#une image toute les 15 ms
		
		#test evenements, arrêt de jeu :
		testEvenements()

Code final

Le code final est le suivant :
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *
#  variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
couleurFond = [255, 0, 0,10]#rouge
couleurBalle= [0,0,255]#les balles ont la même couleur
#balle
dx = [3,2,5] #vecteur de déplacement entre deux frames consécutives
dy = [-1, 1,3]
x = [100, 250, 50]
y = [30, 400, 100]
r = [20, 25, 15]

# Nos fonctions d'actions
def afficher(i):
	global x,y,couleurBalle,r
	circle(screen, couleurBalle , [x[i],y[i]], r[i], 0)
	
def avancer(i):
	global x,y,dx,dy
	x[i]=x[i]+dx[i]
	y[i]=y[i]+dy[i]
def testCollision(i):
	global x,y,width,height,r,dx,dy
	#bords verticaux
	if x[i]+r[i] > width or x[i]-r[i] < 0:
   dx[i]=-dx[i]
#bords horizontaux
if y[i]+r[i] > height or y[i]-r[i] < 0:
   dy[i]=-dy[i]
def testEvenements():
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	
	#afficher la balle :
	afficher(0)
afficher(1)
afficher(2)
#déplacement de la balle :
avancer(0)
avancer(1)
avancer(2)
#test de collision
testCollision(0)
testCollision(1)
testCollision(2)
	
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test evenements, arrêt de jeu :
	testEvenements()
 
code source : pong2.0.py
Modifier le code précédent pour donner une couleur différente à chaque balle en utilisant une liste de couleurs.
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *
#  variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
couleurFond = [255, 0, 0,10]#rouge
#balle
dx = [3,2,5] #vecteur de déplacement entre deux frames consécutives
dy = [-1, 1,3]
x = [100, 250, 50]
y = [30, 400, 100]
r = [20, 25, 15]
#tableau de couleur
couleurBalle1= [0,0,255]#les balles ont la même couleur
couleurBalle2= [255,0,255]#les balles ont la même couleur
couleurBalle3= [255,255,255]#les balles ont la même couleur
couleurBalle = [couleurBalle1,couleurBalle2,couleurBalle3]
# Nos fonctions d'actions
def afficher(i):
	global x,y,couleurBalle,r
	circle(screen, couleurBalle[i] , [x[i],y[i]], r[i], 0)
def avancer(i):
	global x,y,dx,dy
	x[i]=x[i]+dx[i]
	y[i]=y[i]+dy[i]
def testCollision(i):
	global x,y,width,height,r,dx,dy
	#bords verticaux
	if x[i]+r[i] > width or x[i]-r[i] < 0:
	   dx[i]=-dx[i]
	#bords horizontaux
	if y[i]+r[i] > height or y[i]-r[i] < 0:
	   dy[i]=-dy[i]
def testEvenements():
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	#afficher la balle :
	afficher(0)
	afficher(1)
	afficher(2)
	#déplacement de la balle :
	avancer(0)
	avancer(1)
	avancer(2)
	#test de collision
	testCollision(0)
	testCollision(1)
	testCollision(2)
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test evenements, arrêt de jeu :
	testEvenements()
code source : pong2.1.py
Nous souhaitons pouvoir afficher un nombre quelconque de balles. On propose une méthode plus générale pour déclarer les listes qui s'adaptera beaucoup mieux à la gestion d'un grand nombre de balles.
  • On déclare une liste vide
  • On ajoute chaque élément de la liste l'un après l'autre avec la méthode
    append
    vue précédemment
Cela permettra de plus d'ajouter ou de retirer des balles pendant le jeu.
Appliquer la méthode générale pour modifier dans le code précédent la déclaration des listes
dx
,
dy
,
x
,
y
et
r
.
# déclaration des listes vides des attributs des balles
dx = [] #vecteur de déplacement entre deux frames consécutives
dy = []
x = []
y = []
r = []
#initialisation des listes
dx.append(3)
dx.append(2)
dx.append(5)
dy.append(-1)
dy.append(1)
dy.append(3)
x.append(100)
x.append(250)
x.append(50)
y.append(100)
y.append(400)
y.append(100)
r.append(20)
r.append(25)
r.append(15)
code source : pong2.2.py
Cette méthode semble pour l'instant plus longue que la précédente, mais s'adaptera parfaitement à un nombre de balles plus important.
En prévision d'un nombre de balles important, on souhaite ne pas avoir à gérer les valeurs de tous les attributs à la main. Nous proposons de les générer aléatoirement. Nous proposons de faire appel à la librairie python
random
On va tirer au hasard des nombres entiers. Pour cela on n'aura besoin que de la fonction
randint()
de la librairie
random
. On l'importe de la manière suivante :

from random import randint
Pour tirer un nombre au hasard entre
valMin
et
valMax
(compris), on utilise :

randint(valMin, valMax)
Appliquer cette méthode pour donner des valeurs aléatoires aux éléments des listes
dx
,
dy
,
x
,
y
,
r
ET
couleurBalle
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *
from random import randint
#  variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
couleurFond = [255, 0, 0,10]#rouge
couleurBalle1= [0,0,255]#les balles ont la même couleur
dxMin=-5
dxMax=5
dyMin=-5
dyMax=5
rmax = 40
rmin = 10
# déclaration des listes vides des attributs des balles
dx = [] #vecteur de déplacement entre deux frames consécutives
dy = []
x = []
y = []
r = []
#initialisation des listes
dx.append(randint(dxMin,dxMax))
dx.append(randint(dxMin,dxMax))
dx.append(randint(dxMin,dxMax))
dy.append(randint(dyMin,dyMax))
dy.append(randint(dyMin,dyMax))
dy.append(randint(dyMin,dyMax))
x.append(randint(rmax,width-rmax))
x.append(randint(rmax,width-rmax))
x.append(randint(rmax,width-rmax))
y.append(randint(rmax,height-rmax))
y.append(randint(rmax,height-rmax))
y.append(randint(rmax,height-rmax))
r.append(randint(rmin,rmax))
r.append(randint(rmin,rmax))
r.append(randint(rmin,rmax))
#tableau de couleur (dégradé de bleu)
couleurBalle1= [0,0,randint(0,255)]
couleurBalle2= [0,0,randint(0,255)]
couleurBalle3= [0,0,randint(0,255)]
couleurBalle = [couleurBalle1,couleurBalle2,couleurBalle3]
# Nos fonctions d'actions
def afficher(i):
	global x,y,couleurBalle,r
	circle(screen, couleurBalle[i] , [x[i],y[i]], r[i], 0)
def avancer(i):
	global x,y,dx,dy
	x[i]=x[i]+dx[i]
	y[i]=y[i]+dy[i]
def testCollision(i):
	global x,y,width,height,r,dx,dy
	#bords verticaux
	if x[i]+r[i] > width or x[i]-r[i] < 0:
	   dx[i]=-dx[i]
	#bords horizontaux
	if y[i]+r[i] > height or y[i]-r[i] < 0:
	   dy[i]=-dy[i]
def testEvenements():
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	#afficher la balle :
	afficher(0)
	afficher(1)
	afficher(2)
	#déplacement de la balle :
	avancer(0)
	avancer(1)
	avancer(2)
	#test de collision
	testCollision(0)
	testCollision(1)
	testCollision(2)
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test evenements, arrêt de jeu :
	testEvenements()
code source : pong2.3.py
La boucle itérative « for » permets l’exécution d’un bloc d’instructions un certain nombre de fois, en faisant généralement varier un indice entre une valeur initiale et une valeur finale avec un pas d’incrémentation. Lorsque le nombre N de balle est grand, il ne sera très fastidieux déclarer et d'initialiser "à la main" chacun des 6 paramètres de chaque balle. En effet, supposons que nous créons 30 balles, le nombre de paramètres à initialiser est 30*6=180. La solution est d'utiliser une boucle itérative
for
qui permet l’exécution d’un bloc d’instructions un certain nombre de fois, en faisant généralement varier un indice entre une valeur initiale et une valeur finale avec un pas d’incrémentation
for i in range(0,5):
	print("l'indice vaut : ", i )
Ce code affichera :
l'indice vaut : 0
l'indice vaut : 1
l'indice vaut : 2
l'indice vaut : 3
l'indice vaut : 4
L'indice
i
varie d'une valeur initiale
i=0
à une valeur finale
i=4
et non 5 !!.
Une petite précision concernant
range()
Si on exécute dans la console
list(range(0,5))
, on obtient :
[0, 1, 2, 3, 4]
Cette commande crée une liste de 5 éléments de 0 à 4 avec un pas de 1.
Tapez dans la console les commandes suivantes pour comprendre le fonctionnement de
range
.
list(range(2,5))
list(range(0,100,10))
list(range(10,0,-1))
Quel est le rôle du troisième argument ?
  • Le troisième argument représente le pas d'incrémentation de la liste
  • La dernière valeur n'est pas atteinte
  • Le pas peut être négatif
  • Le pas ne peut être qu'entier
On comprend donc que la boucle for peut itérer sur n'importe quelle liste :
L=["Marseille","New York","Alger","Tokyo","Dakar"]
for ville in L:
	print("J'adore la ville de ", ville )
Ce code affichera :
J'adore la ville de  Marseille
J'adore la ville de  New York
J'adore la ville de  Alger
J'adore la ville de  Tokyo
J'adore la ville de  Dakar
Il est maintenant possible d’appliquer la boucle « for » pour simplifier l’écriture des taches itératives :
  • Pour l’initialisation des différents tableaux dans le setup() : d, x, y, dx, dy, c.
  • Pour l’appel aux différentes méthodes appliquées à chaque balle : affiche(i), avancer(i) et testCollision(i).
Modifier le code du pong précédent pour gérer les instruction répétitives écrites à la main à l'aide de boucles for.
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *
from random import randint
#  variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
couleurFond = [255, 0, 0,10]#rouge
couleurBalle1= [0,0,255]#les balles ont la même couleur
rmax = 40
rmin = 10
# déclaration des listes vides des attributs des balles
dx = [] #vecteur de déplacement entre deux frames consécutives
dy = []
x = []
y = []
r = []
couleurBalle = []
#initialisation des listes

for i in range(0,3):
	dx.append(randint(-5,5))
	dy.append(randint(-5,5))
	x.append(randint(width-rmax,height-rmax))
	y.append(randint(width-rmax,height-rmax))
	r.append(randint(rmin,rmax))
	#tableau de couleur (dégradé de bleu)
	couleurBalle.append([0,0,randint(0,255)])

# Nos fonctions d'actions
def afficher(i):
	global x,y,couleurBalle,r
	circle(screen, couleurBalle[i] , [x[i],y[i]], r[i], 0)
def avancer(i):
	global x,y,dx,dy
	x[i]=x[i]+dx[i]
	y[i]=y[i]+dy[i]
def testCollision(i):
	global x,y,width,height,r,dx,dy
	#bords verticaux
	if x[i]+r[i] > width or x[i]-r[i] < 0:
	   dx[i]=-dx[i]
	#bords horizontaux
	if y[i]+r[i] > height or y[i]-r[i] < 0:
	   dy[i]=-dy[i]
def testEvenements():
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	
	for i in range(0,3):
	#afficher la balle :
	afficher(i)
	#déplacement de la balle :
	avancer(i)
	#test de collision
	testCollision(i)

	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test evenements, arrêt de jeu :
	testEvenements()
code source : pong2.4.py
L'intérêt des boucles for est que maintenant, la structure du programme pour afficher 1, 3, 10 ou 30 balles est la même. Introduire une nouvelle variable globale
N
qui représente le nombre de balles. Modifier le code précédent pour afficher N balles. Testez différentes valeurs.
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *
from random import randint
#  variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
couleurFond = [255, 0, 0,10]#rouge
couleurBalle1= [0,0,255]#les balles ont la même couleur
rmax = 40
rmin = 10
N = 10 #nb de balles
# déclaration des listes vides des attributs des balles
dx = [] #vecteur de déplacement entre deux frames consécutives
dy = []
x = []
y = []
r = []
couleurBalle = []
#initialisation des listes
for i in range(0,N):
	dx.append(randint(-5,5))
	dy.append(randint(-5,5))
	x.append(randint(width-rmax,height-rmax))
	y.append(randint(width-rmax,height-rmax))
	r.append(randint(rmin,rmax))
	#tableau de couleur (dégradé de bleu)
	couleurBalle.append([0,0,randint(0,255)])
# Nos fonctions d'actions
def afficher(i):
	global x,y,couleurBalle,r
	circle(screen, couleurBalle[i] , [x[i],y[i]], r[i], 0)
def avancer(i):
	global x,y,dx,dy
	x[i]=x[i]+dx[i]
	y[i]=y[i]+dy[i]
def testCollision(i):
	global x,y,width,height,r,dx,dy
	#bords verticaux
	if x[i]+r[i] > width or x[i]-r[i] < 0:
	   dx[i]=-dx[i]
	#bords horizontaux
	if y[i]+r[i] > height or y[i]-r[i] < 0:
	   dy[i]=-dy[i]
def testEvenements():
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	for i in range(0,N):
		#afficher la balle :
		afficher(i)
		#déplacement de la balle :
		avancer(i)
		#test de collision
		testCollision(i)
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test evenements, arrêt de jeu :
	testEvenements()
code source : pong2.5.py
La raquette est positionnée à gauche par convention. Son abscisse est donc fixe, et son ordonnée sera commandée par des interaction clavier. On ajoute donc plusieurs variables pour représenter la raquette :
  • rx = 20
    : abscisse de la raquette
  • ry = height/2
    : ordonnée de la raquette (positionnée au milieu initialement mais qui variera)
  • dry = 5
    : déplacement vertical de la raquette
  • rh = 100
    : hauteur de la raquette
  • rw = 20
    : largeur de la raquette
  • rcolor = [255, 255, 255]
    : couleur de la raquette (ici blanc)
Pour représenter graphique la raquette, il suffit de dessiner un rectangle. On utilise la methode
 pygame.draw.rect()
qui s'utilise de la manière suivante :
rect(screen, rcolor, [rx, ry, rw, rh])
Afficher une raquette blanche dans la fenêtre du jeu avec les caractéristiques précédentes
On ajoute les variables et une fonction
afficher_raquette()
:
#importation de la librairie pygame
import pygame,sys
from pygame.draw import *
from pygame.display import *
from random import randint
#  variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
couleurFond = [255, 0, 0,10]#rouge
couleurBalle1= [0,0,255]#les balles ont la même couleur
rmax = 40
rmin = 10
N = 10 #nb de balles
# déclaration des listes vides des attributs des balles
dx = [] #vecteur de déplacement entre deux frames consécutives
dy = []
x = []
y = []
r = []
couleurBalle = []
#déclaration des attributs de la raquette
rx=20
ry=height/2
dry=5
rh=100
rw=20
rcolor=[255,255,255]
#initialisation des listes
for i in range(0,N):
	dx.append(randint(-5,5))
	dy.append(randint(-5,5))
	x.append(randint(width-rmax,height-rmax))
	y.append(randint(width-rmax,height-rmax))
	r.append(randint(rmin,rmax))
	#tableau de couleur (dégradé de bleu)
	couleurBalle.append([0,0,randint(0,255)])
# Nos fonctions d'actions
def afficher(i):
	global x,y,couleurBalle,r
	circle(screen, couleurBalle[i] , [x[i],y[i]], r[i], 0)
def avancer(i):
	global x,y,dx,dy
	x[i]=x[i]+dx[i]
	y[i]=y[i]+dy[i]
def testCollision(i):
	global x,y,width,height,r,dx,dy
	#bords verticaux
	if x[i]+r[i] > width or x[i]-r[i] < 0:
	   dx[i]=-dx[i]
	#bords horizontaux
	if y[i]+r[i] > height or y[i]-r[i] < 0:
	   dy[i]=-dy[i]
def testEvenements():
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
#fonctions de la raquette
def afficher_raquette():
	rect(screen, rcolor, [rx, ry, rw, rh])


#lancement des fonctionnalités de la librairie pygame
pygame.init()
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	for i in range(0,N):
		#afficher la balle :
		afficher(i)
		#déplacement de la balle :
		avancer(i)
		#test de collision
		testCollision(i)

	afficher_raquette()
	
	
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test evenements, arrêt de jeu :
	testEvenements()
code source : pong3.0.py
On a besoin d'interagir avec le clavier (flèche du haut et flèche du bas) pour commander la position de la raquette. On va importer les importer les attributs de pygame au début du programme en ajoutant la ligne suivante en entête du programme :
from pygame.locals import *
Cela nous permettra de taper
KEYDOWN
ou
QUIT
ou
K_UP
, etc. au lieu de
pygame.KEYDOWN
ou
pygame.QUIT
ou
pygame.K_UP
, etc.
L'appui sur une touche est considéré par pygame comme un nouvel évènement. Nous avons déjà créé la fonction
testEvenements()
qui gère l'évènement "quitter la fenêtre de jeu". Si l'évènement vaut
pygame.KEYDOWN
, c'est que une touche (n'importe laquelle) a été appuyée.
On modifie maintenant la fonction
testEvenement()
comme ceci :
def testEvenements():
	for event in pygame.event.get():
		if event.type == QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
		if event.type == KEYDOWN:
	print("Une touche vient d'être pressée")
Pour l'instant appuyer sur une touche ne produit aucun résultat visible sur la raquette, et ne fait qu'afficher un message dans la console. La prochaine étape est de détecter quelle touche a été pressée.
  • La méthode
    pygame.key.get_pressed()
    renvoie une liste représentant l'état de chaque touche du clavier : 1 pour appuyée, 0 pour relâchée.
  • L'indice de la touche "flèche du haut" dans le tableau
    pygame.key.get_pressed()
    est 273, celui de "flèche du bas" est 274
  • Pour ne pas avoir à retenir ces indices, pygame a défini des constantes :
    K_UP
    vaut 273 et
    K_DOWN
    vaut 274.
  • Les noms de toutes les constantes associées à chaque touche sont disponibles sur le site de pygame.
On donne la fonction
bouger_raquette()
suivante :
def bouger_raquette():
	global ry, dry
	if pygame.key.get_pressed()[K_UP]:
		#déplacer vers le haut
		....
		
	if pygame.key.get_pressed()[K_DOWN]:
		#déplacer vers le bas
		....
Compléter le code de la fonction
bouge_raquette()
afin de réaliser le déplacement souhaité.
Appeler cette fonction lorsqu'un évènement
KEYDOWN
est détecté
def testEvenements():
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
		if event.type == KEYDOWN:
			bouger_raquette()
def bouger_raquette():
	global ry, dry
	if pygame.key.get_pressed()[K_UP]:
		ry-=dry
	if pygame.key.get_pressed()[K_DOWN]:
		ry+=dry
Vous avez certainement remarqué qu'il faut appuyer plusieurs fois sur les touches bas et haut pour déplacer le raquette. Maintenir une touche appuyée ne déclenche qu'un seul évènement clavier. Pour obliger pygame à vérifier les évenements claviers toutes les 10ms (par exemple), il faut ajouter la ligne suivante juste après avoir initialisé pygame :
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#vérifier les interactions clavier toutes les 10ms
pygame.key.set_repeat(10, 10)
On obtient donc le code entier suivant :
#importation de la librairie pygame
from pygame.locals import *
import pygame,sys
from pygame.draw import *
from pygame.display import *
from random import randint
#  variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
couleurFond = [255, 0, 0,10]#rouge
couleurBalle1= [0,0,255]#les balles ont la même couleur
rmax = 40
rmin = 10
N = 10 #nb de balles
# déclaration des listes vides des attributs des balles
dx = [] #vecteur de déplacement entre deux frames consécutives
dy = []
x = []
y = []
r = []
couleurBalle = []
#déclaration des attributs de la raquette
rx=20
ry=height/2
dry=2
rh=100
rw=20
rcolor=[255,255,255]

#initialisation des listes
for i in range(0,N):
	dx.append(randint(-5,5))
	dy.append(randint(-5,5))
	x.append(randint(width-rmax,height-rmax))
	y.append(randint(width-rmax,height-rmax))
	r.append(randint(rmin,rmax))
	#tableau de couleur (dégradé de bleu)
	couleurBalle.append([0,0,randint(0,255)])
# Nos fonctions d'actions
def afficher(i):
	global x,y,couleurBalle,r
	circle(screen, couleurBalle[i] , [x[i],y[i]], r[i], 0)
def avancer(i):
	global x,y,dx,dy
	x[i]=x[i]+dx[i]
	y[i]=y[i]+dy[i]
def testCollision(i):
	global x,y,width,height,r,dx,dy
	#bords verticaux
	if x[i]+r[i] > width or x[i]-r[i] < 0:
	   dx[i]=-dx[i]
	#bords horizontaux
	if y[i]+r[i] > height or y[i]-r[i] < 0:
	   dy[i]=-dy[i]
def testEvenements():
	for event in pygame.event.get():
		if event.type == QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
		if event.type == KEYDOWN:
	bouger_raquette()

#fonctions de la raquette
def afficher_raquette():
	rect(screen, rcolor, [rx, ry, rw, rh])
def bouger_raquette():
	global ry, dry
	if pygame.key.get_pressed()[K_UP]:
		ry-=dry
	if pygame.key.get_pressed()[K_DOWN]:
		ry+=dry

#lancement des fonctionnalités de la librairie pygame
pygame.init()

#vérifier les interactions clavier toutes les 10ms
pygame.key.set_repeat(10, 10)
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	for i in range(0,N):
		#afficher la balle :
		afficher(i)
		#déplacement de la balle :
		avancer(i)
		#test de collision
		testCollision(i)
	afficher_raquette()
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test evenements, arrêt de jeu :
	testEvenements()
code source : pong3.1.py
Nous proposons ici de modifier la fonction
testCollision(i)
qui teste la collision de la balle
i
avec les bords. Il est nécessaire de faire un schéma pour bien comprendre la situation : Pour qu’il y ait collision entre la balle et la raquette, il faut que le point N de la ième balle soit tangent ou traverse légèrement le bord droit de la raquette. Les conditions suivantes doivent simultanément être respectées :
  • Suivant x : l’abscisse du point N doit être inférieure à celle du point A et B, soit, x – r \lt rx + rw
  • Suivant y : l’ordonnée du point N doit être comprise entre l’ordonnée du point A et l’ordonnée du point B, soit, ry \lt y \lt ry + rh .
Algorithmiquement, il faut donc que la condition ry \lt y et que la condition y \lt ry + rh et que la condition x – r \lt rx+rw soient vérifiées simultanément. En programmation Python, le « et logique » s’écrit
and
. Pour la i-ème balle, cette condition logique s'écrit donc :
(ry < y[i]) and (y[i] < (ry + rh)) and ((x[i] – r[i]) < (rx+rw))
Modifier la fonction
testCollision
pour que le bord droit de la raquette se comporte comporte comme le bord droit de l'écran et fasse rebondir la balle.
def testCollision(i):
	global x,y,width,height,r,dx,dy,ry,rh,rw
	#bords verticaux
	if x[i]+r[i] > width or x[i]-r[i] < 0:
	   dx[i]=-dx[i]
	#bords horizontaux
	if y[i]+r[i] > height or y[i]-r[i] < 0:
	   dy[i]=-dy[i]
	#rebond raquette :
if (ry < y[i]) and (y[i] < (ry + rh)) and ((x[i] - r[i]) < (rx + rw)):
	dx[i]=-dx[i]
Ce qui produit le programme final suivant :
#importation de la librairie pygame
from pygame.locals import *
import pygame,sys
from pygame.draw import *
from pygame.display import *
from random import randint
#  variables globales ##########################################################"
size = width, height = 500, 500 # taille de la fenêtre d'affichage
couleurFond = [255, 0, 0,10]#rouge
couleurBalle1= [0,0,255]#les balles ont la même couleur
rmax = 40
rmin = 10
N = 10 #nb de balles
# déclaration des listes vides des attributs des balles
dx = [] #vecteur de déplacement entre deux frames consécutives
dy = []
x = []
y = []
r = []
couleurBalle = []
#déclaration des attributs de la raquette
rx=20
ry=height/2
dry=2
rh=100
rw=20
rcolor=[255,255,255]
#initialisation des listes
for i in range(0,N):
	dx.append(randint(-5,5))
	dy.append(randint(-5,5))
	x.append(randint(width-rmax,height-rmax))
	y.append(randint(width-rmax,height-rmax))
	r.append(randint(rmin,rmax))
	#tableau de couleur (dégradé de bleu)
	couleurBalle.append([0,0,randint(0,255)])
# Nos fonctions d'actions
def afficher(i):
	global x,y,couleurBalle,r
	circle(screen, couleurBalle[i] , [x[i],y[i]], r[i], 0)
def avancer(i):
	global x,y,dx,dy
	x[i]=x[i]+dx[i]
	y[i]=y[i]+dy[i]
def testCollision(i):
	global x,y,width,height,r,dx,dy,ry,rh,rw
	#bords verticaux
	if x[i]+r[i] > width or x[i]-r[i]  0:
	   dx[i]=-dx[i]
	#bords horizontaux
	if y[i]+r[i] > height or y[i]-r[i] < 0:
	   dy[i]=-dy[i]
	if (ry < y[i]) and (y[i] < (ry + rh)) and ((x[i] - r[i]) < (rx + rw)):
		dx[i]=-dx[i]
def testEvenements():
	for event in pygame.event.get():
		if event.type == pygame.QUIT:#si clique sur la croix quitter
			pygame.display.quit()#ferme la fenêtre
			sys.exit()#arrête le programme
		if event.type == KEYDOWN:
			bouger_raquette()
#fonctions de la raquette
def afficher_raquette():
	rect(screen, rcolor, [rx, ry, rw, rh])
def bouger_raquette():
	global ry, dry
	if pygame.key.get_pressed()[K_UP]:
		ry-=dry
	if pygame.key.get_pressed()[K_DOWN]:
		ry+=dry
#lancement des fonctionnalités de la librairie pygame
pygame.init()
#vérifier les interactions clavier toutes les 10ms
pygame.key.set_repeat(10, 10)
#affichage d'une fenêtre de dessin de largeur 500 pixels et de hauteur 500 pixels
screen = set_mode(size)
while True:
	#remplissage du fond par la couleur blanche
	screen.fill(couleurFond)
	for i in range(0,N):
		#afficher la balle :
		afficher(i)
		#déplacement de la balle :
		avancer(i)
		#test de collision
		testCollision(i)
	afficher_raquette()
	flip()#activer le rendu graphique
	pygame.time.delay(15)#une image toute les 15 ms
	#test evenements, arrêt de jeu :
	testEvenements()
code source : pong3.1.py

Le pong réalisé est ainsi jouable. Il ne s'agit bien sûr pas d'un jeu finalisé. Il nous aura permis d'introduire toutes les notions fondamentales de programmation python ainsi que des morceaux de code réutilisables pour d'autres types de projets.

Il est possible de poursuivre et d'améliorer le jeu :

  • La programmation objet est sans doute plus adapter pour un jeu vidéo. Il serait mieux de définir la raquette et les balles comme des instances de classes. Les variables globales ne sont pas forcément une manière très "propre" de réaliser un jeu, et la programmation objet permettrait d'y remédier en utilisant des attributs de classe.
  • On peut aliorer le jeu en prévoyant des collisions sur les quatre bords de la raquette. La classe
    pygame.Rect
    est un outil que nous n'avons pas utilisé mais qui est plus adapté aux problèmes de collision.
  • Il serait plus intéressant de prévoir un scénario de jeu complet avec un autre joueur, une possibilité de perdre, un score, des sons, etc.
  • Enfin, d'autres types de jeu 2D basiques (plateforme, puzzle, jeux de rôles, narration interactive, ...) sont envisageables comme projets pour les élèves/étudiants avec tous les outils découverts dans ce tutoriel.

  • Johan SEGURA est professeur de Mathématique au lycée Langevin à Martigues
  • Damien MUTI DESGROUAS est professeur de Physique-Chimie au lycée Saint Exupéry et Diderot à Marseille.
  1. DSAA : Diplôme Supérieur d’Arts Appliqués
  2. IDE signifie Integrated Development Environment, ou Environnement de Développement.
  3. Les variables globales sont généralement à éviter dans des projets plus complexes. D'autres solutions sont préférables, notamment les attributs d'une classe en programmation orientée objet.
  4. fps = "frames per second" ou "images par seconde" dans la langue de Jul
  5. random signifie aléatoire en anglais
  1. DSAA : Diplôme Supérieur d’Arts Appliqués
  2. IDE signifie Integrated Development Environment, ou Environnement de Développement.
  3. Les variables globales sont généralement à éviter dans des projets plus complexes. D'autres solutions sont préférables, notamment les attributs d'une classe en programmation orientée objet.
  4. fps = "frames per second" ou "images par seconde" dans la langue de Jul
  5. random signifie aléatoire en anglais